/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// Module MUST be compiled with structure allignment set to a maximum
// of 1 byte ( zp1 ).

#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include "gusmidi.h"

#define TRUE  ( 1 == 1 )
#define FALSE ( !TRUE )

#if defined(__WATCOMC__) && defined(__FLAT__)
   #define LOADDS _loadds
   #define FAR
   typedef void ( __interrupt __far *PRI )();
   #define outportb outp
   #define enable _enable
   #define disable _disable
   #define farfree free
   #define farmalloc malloc
#else
   #define LOADDS
   #define FAR far
   typedef void ( interrupt *PRI )();
#endif

#include "newgf1.h"

#define	BUFFER 2048U     /* size of DMA buffer for patch loading */

#define MAX_MEM_CONFIG   3

#define NUM_PATCHES 256  /* size of patch array (128 perc, 128 melodic) */
#define BIGGEST_NAME 9   /* size of largest patch name */

#define PATCH_LOADED 1   /* patch loading flags */

#define UNUSED_PATCH -1

static struct patch       new_patch[ NUM_PATCHES ];
static unsigned char FAR *patch_waves[ NUM_PATCHES ];

static int   patch_map[ NUM_PATCHES ][ MAX_MEM_CONFIG + 1 ];
static char  program_name[ NUM_PATCHES ][ BIGGEST_NAME ];
static char  patch_flags[ NUM_PATCHES ];

// DEBUG
// static char  config_name[] = "ULTRASND.INI";

static char  config_name[] = "ULTRAMID.INI";
static char  config_dir[ 80 ] =
   {
   '\0'
   };

static char  ultradir[ 80 ];   /* The name of the configuration directory */
static char *hold_buffer;

static int GUSMIDI_Volume = 255;

static unsigned long GUS_TotalMemory;
static int           GUS_MemConfig;

static int GUS_Installed = 0;

int GUS_ErrorCode = GUS_Ok;
int GUS_AuxError  = 0;

#define GUS_SetErrorCode( status ) \
   GUS_ErrorCode   = ( status );


/*---------------------------------------------------------------------
   Function: GUS_ErrorString

   Returns a pointer to the error message associated with an error
   number.  A -1 returns a pointer the current error.
---------------------------------------------------------------------*/

char *GUS_ErrorString
   (
   int ErrorNumber
   )

   {
   char *ErrorString;

   switch( ErrorNumber )
      {
      case GUS_Error :
         ErrorString = GUS_ErrorString( GUS_ErrorCode );
         break;

      case GUS_Ok :
         ErrorString = "Ultrasound music ok.";
         break;

      case GUS_OutOfMemory :
         ErrorString = "Out of memory in GusMidi.";
         break;

      case GUS_OutOfDosMemory :
         ErrorString = "Out of conventional (640K) memory in GusMidi.";
         break;

      case GUS_GF1Error :
         ErrorString = gf1_error_str( GUS_AuxError );
         break;

      case GUS_InvalidIrq :
         ErrorString = "Ultrasound IRQ must be 7 or less.";
         break;

      case GUS_ULTRADIRNotSet :
         ErrorString = "ULTRADIR environment variable not set.";
         break;

      case GUS_MissingConfig :
         ErrorString = "Can't find ULTRASND.INI file.";
         break;

      case GUS_FileError :
         ErrorString = strerror( GUS_AuxError );
         break;

      default :
         ErrorString = "Unknown Ultrasound error code.";
         break;
      }

   return( ErrorString );
   }


/*
int GUS_GetPatchMap
   (
   char *name
   )

   {
   char text[ 80 ];
   char FAR *ud;
   int  index;
   char pname[ 80 ];
   FILE *fp;

   ud = getenv( "ULTRADIR" );
   if ( ud == NULL )
      {
      GUS_SetErrorCode( GUS_ULTRADIRNotSet );
      return( GUS_Error );
      }

   strcpy( ultradir, ud );
   strcat( ultradir, "\\midi\\" );
   strcpy( config_dir, ud );
   strcat( config_dir, "\\" );
   strcpy( text, config_dir );
   strcat( text, name );

   fp = fopen( text, "r" );
   if ( fp == NULL )
      {
      GUS_SetErrorCode( GUS_MissingConfig );
      return( GUS_Error );
      }

   while( 1 )
      {
      if ( fgets( text, 80, fp ) == NULL )
         {
         break;
         }

      if ( strncmp( text, "[Drum Patches]", 13 ) == 0 )
         {
         while( 1 )
            {
            if ( fgets( text, 80, fp ) == NULL )
               {
               break;
               }

            if ( text[ 0 ] == ';' )
               {
               continue;
               }

            if ( text[ 0 ] == '[' )
               {
               break;
               }

            if ( sscanf( text, "%d=%s\n", &index, pname ) != 2 )
               {
               continue;
               }

            strcpy( program_name[ index + 128 ], pname );
            }

         break;
         }
      }

   if ( fseek( fp, 0L, SEEK_SET ) )
      {
      GUS_AuxError = errno;
      GUS_SetErrorCode( GUS_FileError );
      return( GUS_Error );
      }

   while( 1 )
      {
      if ( fgets( text, 80, fp ) == NULL )
         {
         break;
         }

      if ( strncmp( text, "[Melodic Patches]", 17 ) == 0 )
         {
         while( 1 )
            {
            if ( fgets( text, 80, fp ) == 0 )
               {
               break;
               }

            if ( text[ 0 ] == ';' )
               {
               continue;
               }

            if ( text[ 0 ] == '[' )
               {
               break;
               }

            if ( sscanf( text, "%d=%s\n", &index, pname ) != 2 )
               {
               continue;
               }

            strcpy( program_name[ index ], pname );
            }
         break;
         }
      }

   fclose( fp );

   return( GUS_Ok );
   }
*/

int GUS_GetPatchMap
   (
   char *name
   )

   {
   char text[ 80 ];
   char FAR *ud;
   int  index;
   int  ignore;
   FILE *fp;

   for( index = 0; index < NUM_PATCHES; index++ )
      {
      patch_map[ index ][ 0 ] = UNUSED_PATCH;
      patch_map[ index ][ 1 ] = UNUSED_PATCH;
      patch_map[ index ][ 2 ] = UNUSED_PATCH;
      patch_map[ index ][ 3 ] = UNUSED_PATCH;
      program_name[ index ][ 0 ] = 0;
      }

   ud = getenv( "ULTRADIR" );
   if ( ud == NULL )
      {
      GUS_SetErrorCode( GUS_ULTRADIRNotSet );
      return( GUS_Error );
      }

   strcpy( ultradir, ud );
   strcat( ultradir, "\\midi\\" );
   strcpy( config_dir, ud );
   strcat( config_dir, "\\midi\\" );
   strcpy( text, ultradir );
   strcat( text, name );

   fp = fopen( text, "r" );
   if ( fp == NULL )
      {
      GUS_SetErrorCode( GUS_MissingConfig );
      return( GUS_Error );
      }

   while( 1 )
      {
      if ( fgets( text, 80, fp ) == NULL )
         {
         break;
         }

      if ( text[ 0 ] == '#' )
         {
         continue;
         }

      if ( sscanf( text, "%d", &index ) != 1 )
         {
         continue;
         }

      sscanf( text, "%d, %d, %d, %d, %d, %s\n", &ignore,
         &patch_map[ index ][ 0 ], &patch_map[ index ][ 1 ],
         &patch_map[ index ][ 2 ], &patch_map[ index ][ 3 ],
         program_name[ index ] );
      }

   fclose( fp );

   return( GUS_Ok );
   }

int GUSMIDI_UnloadPatch
   (
   int prognum
   )

   {
   int  prog;

   prog = patch_map[ prognum ][ GUS_MemConfig ];

   if ( patch_flags[ prog ] & PATCH_LOADED )
      {
      disable();

      gf1_unload_patch( &new_patch[ prog ] );
      if ( patch_waves[ prog ] != NULL )
         {
         free( patch_waves[ prog ] );
         patch_waves[ prog ] = NULL;
         }

      /* just in case sequence is still playing */
      new_patch[ prog ].nlayers = 0;
      patch_flags[ prog ] &= ~PATCH_LOADED;

      enable();
      }

   return( GUS_Ok );
   }

int GUSMIDI_LoadPatch
   (
   int prognum
   )

   {
   int  prog;
   char text[ 80 ];
   int  ret;
   unsigned char FAR *wave_buff;
   struct patchinfo  patchi;

   prog = patch_map[ prognum ][ GUS_MemConfig ];

   if ( ( patch_flags[ prog ] & PATCH_LOADED ) || ( prog == UNUSED_PATCH ) )
      {
      return( GUS_Ok );
      }

   if ( !program_name[ prog ][ 0 ] )
      {
      return( GUS_Ok );
      }

   strcpy( text, ultradir );
   strcat( text, program_name[ prog ] );
   strcat( text, ".pat" );

   ret = gf1_get_patch_info( text, &patchi );
   if ( ret != OK )
      {
      GUS_AuxError = ret;
      GUS_SetErrorCode( GUS_GF1Error );
      return( GUS_Error );
      }

   wave_buff = ( unsigned char FAR * )malloc( patchi.header.wave_forms *
      sizeof( struct wave_struct ) );

   if ( wave_buff == NULL )
      {
      GUS_SetErrorCode( GUS_OutOfMemory );
      return( GUS_Error );
      }

   ret = gf1_load_patch( text, &patchi, &new_patch[ prog ], hold_buffer,
      BUFFER, ( unsigned char FAR * )wave_buff, PATCH_LOAD_8_BIT );

   if ( ret != OK )
      {
      free( wave_buff );
      GUS_AuxError = ret;
      GUS_SetErrorCode( GUS_GF1Error );
      return( GUS_Error );
      }

   patch_waves[ prog ]  = wave_buff;
   patch_flags[ prog ] |= PATCH_LOADED;

   return( GUS_Ok );
   }

void *D32DosMemAlloc
   (
   unsigned size
   )

   {
   #if defined(__WATCOMC__) && defined(__FLAT__)
      union REGS r;

      r.x.eax = 0x0100;              /* DPMI allocate DOS memory */
      r.x.ebx = ( size + 15 ) >> 4;  /* Number of paragraphs requested */
      int386( 0x31, &r, &r );
      if ( r.x.cflag )
         {
         return( NULL );   /* Failed */
         }

      return( ( void * )( ( r.x.eax & 0xFFFF ) << 4 ) );
   #else
      return( ( void * )( malloc( size ) ) );
   #endif
   }

void GUSMIDI_ProgramChange
   (
   int channel,
   int prognum
   )

   {
   int  prog;

   prog = patch_map[ prognum ][ GUS_MemConfig ];

   if ( patch_flags[ prog ] & PATCH_LOADED )
      {
      gf1_midi_change_program( &new_patch[ prog ], channel );
      }
   else
      {
      gf1_midi_change_program( NULL, channel );
      }
   }

void GUSMIDI_NoteOn
   (
   int chan,
   int note,
   int velocity
   )

   {
   int prog;

   if ( chan == 9 )
      {
      prog = patch_map[ note + 128 ][ GUS_MemConfig ];

      if ( patch_flags[ prog ] & PATCH_LOADED )
         {
         gf1_midi_note_on( &new_patch[ note + 128 ], 1,
            note, velocity, 9 );
         }
      }
   else
      {
      gf1_midi_note_on( 0L, 1, note, velocity, chan );
      }
   }

#pragma warn -par

void GUSMIDI_NoteOff
   (
   int chan,
   int note,
   int velocity
   )

   {
   gf1_midi_note_off( note, chan );
   }

#pragma warn .par

void GUSMIDI_ControlChange
   (
   int channel,
   int number,
   int value
   )

   {
   gf1_midi_parameter( channel, number, value );
   }

void GUSMIDI_PitchBend
   (
   int channel,
   int lsb,
   int msb
   )

   {
   gf1_midi_pitch_bend( channel, lsb, msb );
   }

void GUSMIDI_ReleasePatches
   (
   void
   )

   {
   int i;

   for( i = 0; i < 256; i++ )
      {
      GUSMIDI_UnloadPatch( i );
      }
   }

void GUSMIDI_SetVolume
   (
   int volume
   )

   {
   volume = max( 0, volume );
   volume = min( volume, 255 );

   GUSMIDI_Volume = volume;
   gf1_midi_master_volume( volume >> 1 ); /* range = 0 to 127 */
   }

int GUSMIDI_GetVolume
   (
   void
   )

   {
   return( GUSMIDI_Volume );
   }

#pragma on (check_stack);

int GUS_Init
   (
   void
   )

   {
   struct load_os os;
   int ret;

   if ( GUS_Installed > 0 )
      {
      GUS_Installed++;
      return( GUS_Ok );
      }

   GUS_SetErrorCode( GUS_Ok );

   GUS_Installed = 0;

   GetUltraCfg( &os );

   #if defined(__WATCOMC__) && defined(__FLAT__)
      if ( os.forced_gf1_irq > 7 )
         {
         GUS_SetErrorCode( GUS_InvalidIrq );
         return( GUS_Error );
         }
   #endif

   os.voices = 24;
   ret = gf1_load_os( &os );
   if ( ret )
      {
      GUS_AuxError = ret;
      GUS_SetErrorCode( GUS_GF1Error );
      return( GUS_Error );
      }

   GUS_Installed = 1;
   return( GUS_Ok );
   }

void GUS_Shutdown
   (
   void
   )

   {
   GUS_SetErrorCode( GUS_Ok );

   if ( GUS_Installed > 0 )
      {
      //printf( "GUS_Installed--;\n" );
      GUS_Installed--;
      if ( GUS_Installed == 0 )
         {
         //printf( "gf1_unload_os();\n" );
         gf1_unload_os();
         }
      }
   //printf( "Gus shutdown exit\n" );
   }

int GUSMIDI_Init
   (
   void
   )

   {
   int ret;
   int i;
   int startmem;

   ret = GUS_Init();
   if ( ret != GUS_Ok )
      {
      return( ret );
      }

   GUS_TotalMemory = gf1_mem_avail();
   GUS_MemConfig   = ( GUS_TotalMemory - 1 ) >> 18;
//   //printf( "Totalmem = %d\n", GUS_TotalMemory );
//   //printf( "Config # = %d\n", GUS_MemConfig );
//   getch();

   if ( GUS_MemConfig < 0 )
      {
      GUS_MemConfig = 0;
      }

   if ( GUS_MemConfig > MAX_MEM_CONFIG )
      {
      GUS_MemConfig = MAX_MEM_CONFIG;
      }

   hold_buffer = D32DosMemAlloc( BUFFER );
   if ( hold_buffer == NULL )
      {
      gf1_unload_os();
      GUS_SetErrorCode( GUS_OutOfDosMemory );
      return( GUS_Error );
      }

   for( i = 0; i < NUM_PATCHES; i++ )
      {
      program_name[ i ][ 0 ] = '\0';
      patch_waves[ i ]       = NULL;
      patch_flags[ i ]       = 0;
      }

   GUSMIDI_SetVolume( 255 );

   /*
   ** GUS_GetPatchMap reads a list of patches and the
   ** associated program numbers out of a configuration file.
   */
   ret = GUS_GetPatchMap( config_name );
   if ( ret != GUS_Ok )
      {
      return( ret );
      }

   startmem = gf1_mem_avail();
   for( i = 0; i < NUM_PATCHES; i++ )
      {
      ret = GUSMIDI_LoadPatch( i );
//      //printf( "%d, %d\n", i, gf1_mem_avail() );

      if ( ret != GUS_Ok )
         {
//         //printf( "Error!\n" );
//         getch();
//      GUS_SetErrorCode( GUS_OutOfDRAM );
//      return( GUS_Error );
         return( ret );
         }
      }

//   //printf( "mem available %d\n", gf1_mem_avail() );
//   getch();
   return( GUS_Ok );
   }

void GUSMIDI_Shutdown
   (
   void
   )

   {
   //printf( "Release Patches\n" );
   GUSMIDI_ReleasePatches();
   //printf( "Shutdown\n" );
   GUS_Shutdown();
   //printf( "Return\n" );
   }
